package org.sciom

import scala.collection.mutable.ArrayBuffer

import org.sciom.parser.ScIomLexer

import org.antlr.runtime.Token

object ScIomMessage {
  val activate = (self: ScIomObject, target: ScIomObject, locals: ScIomObject, m: ScIomMessage, slotContext: ScIomObject) => {
    if(self.isInstanceOf[ScIomMessage]) {
      val selfData = self.asInstanceOf[ScIomMessage]
      selfData.withLocalsAndPerformOn(locals, locals)
    } else {
      // TODO: ERROR
      null
    }
  }

  def newParse(state: ScIomState, lexer: ScIomLexer) : ScIomMessage = {
    // Handle errors here, later

    if(lexer.topToken.getType == ScIomLexer.Terminator) {
      lexer.nextToken
    }

println("newParse with: " + lexer.topToken)
    if(ScIomLexer.isValidMessageName(lexer.topToken)) {
      // and some error handling here too
      return newParseNextMessageChain(state, lexer)
    }

    newWithNameAndReturnsValue(state, 
                                 state.symbolWithString("nil"),
                                 state.ioNil)
  }

  // Io: IoMessage_newFromText_labelSymbol_
  def newFromText(state: ScIomState, text: ScIomSymbol) : ScIomMessage = {
    val lexer = ScIomLexer.getLexerFor(text.string)
    val msg = newParse(state, lexer)
    msg.opShuffle
    msg
  }

  def newParseNextMessageChain(state: ScIomState, lexer: ScIomLexer) : ScIomMessage = {
    val self = new ScIomMessage(state)

    self.parseName(lexer)

    if(lexer.topToken.getType == ScIomLexer.OpenParen) {
      self.parseArgs(lexer)
    }    

    if(ScIomLexer.isValidMessageName(lexer.topToken)) {
      self.parseNext(lexer)
    }    
    
    while(lexer.topToken.getType == ScIomLexer.Terminator) {
      lexer.nextToken

      if(ScIomLexer.isValidMessageName(lexer.topToken)) {
        val eol = newWithName(state, state.semicolonSymbol)
        self.next = eol // This is rawSetNext in the code. Might not map correctly
        eol.parseNext(lexer)
      }    
    }

    self
  }

  def newWithNameAndReturnsValue(state: ScIomState, name: ScIomSymbol, value: ScIomObject) : ScIomMessage = {
    val self = new ScIomMessage(state, name, null)
    self.cachedResult = value
    self
  }

  def newWithName(state: ScIomState, symbol: ScIomSymbol) : ScIomMessage = {
    new ScIomMessage(state, symbol, null)
  }
}

class ScIomMessage(state: ScIomState, var mname: ScIomSymbol, var next: ScIomMessage) extends ScIomObject(state) {
  def this(state: ScIomState) = this(state, state.symbolWithString("[unnamed]"), null)

  var methodTable = List(
    ("opShuffle", (self: ScIomObject, locals: ScIomObject, m: ScIomMessage) => {
      if(self.isInstanceOf[ScIomMessage]) {
        self.asInstanceOf[ScIomMessage].opShuffle(locals, m)
      } else {
        null //TODO ERROR HERE
      }
    }))


  tag = new ScIomTag(state, "Message", ScIomObject.rawClone)
  tag.activateFunc = ScIomMessage.activate

  addMethodTable(methodTable)

  override def allocate() : ScIomMessage = {
    new ScIomMessage(state, mname, next)
  }

  override def toString : String = {
    var nm = "?empty-message?"

    if(mname != null) {
      nm = mname.string
    }

    if(args.internalList.isEmpty) {
      if(next == null) {
        return nm
      } else {
        return nm + " " + next.toString
      }
    } else {
      if(next == null) {
        return nm + "(" + args.internalList.mkString(", ")  + ")"
      } else {
        return nm + "(" + args.internalList.mkString(", ") + ")" + " " + next.toString
      }
    }
  }

  // Io: IoMessage_cachedResult
  // Io: IoMessage_cachedResult_
  var cachedResult: ScIomObject = null
  var args = new ScIomList(state)

  // Io: rawSetName
  // name=

  // Io: rawSetNext
  // next=

  // Methods available in IoCode. Should be able to do something better about these...
  def next(locals: ScIomObject, m: ScIomMessage) : ScIomObject = next
  def setNext(locals: ScIomObject, m: ScIomMessage) = println("WARN: setNext no implemented"); null //TODO: implement
  def protoName(locals: ScIomObject, m: ScIomMessage) = mname
  def protoSetName(locals: ScIomObject, m: ScIomMessage) = println("WARN: protoSetName not implemented"); null  //TODO: implement

  def parseName(lexer: ScIomLexer) = {
    val token = lexer.nextToken
    mname = state.symbolWithString(token.getText)

    ifPossibleCacheToken(token)
  }

  def parseArgs(lexer: ScIomLexer) = println("WARN: parseArgs not implemented"); null //TODO: implement

  def parseNext(lexer: ScIomLexer) : Unit = {
    val nextMessage = ScIomMessage.newParseNextMessageChain(state, lexer)
    next = nextMessage
  }

  def ifPossibleCacheToken(token: Token) : Unit = {
    //TODO: implement
println("WARN: ifPossibleCacheToken not implemented")

    var method = mname
    var r : ScIomObject = null

    var tp = token.getType

    if(tp == ScIomLexer.TriQuote) {
      // TODO: implement
    } else if(tp == ScIomLexer.MonoQuote) {
      r = method.rawAsUnquotedSymbol.rawAsUnescapedSymbol
    } else if(tp == ScIomLexer.Number) {
      // TODO: implement
    } else if(tp == ScIomLexer.HexNumber) {
      // TODO: implement
    } else {
      if(method.string == "nil") {
        r = state.ioNil
      } else if(method.string == "true") {
        r = state.ioTrue
      } else if(method.string == "false") {
        r = state.ioFalse
      }
    }

    cachedResult = r
  }

  // Io: IoMessage_addCachedArg_
  def addCachedArg(arg: ScIomObject) : Unit = {
    val m = new ScIomMessage(state)
    m.cachedResult = arg
    addArg(m)
  }

  // Io: IoMessage_locals_valueArgAt_
  def localsAndValueArgAt(locals: ScIomObject, at: int) : ScIomObject = { 
    localsAndQuickValueArgAt(locals, at)
  }

  // Io: IoMessage_locals_quickValueArgAt_
  def localsAndQuickValueArgAt(locals: ScIomObject, at: int) : ScIomObject = { 
    if(at < args.length) {
      val _m = args.at(at)
      
      if(_m.isInstanceOf[ScIomMessage]) {
        val md = _m.asInstanceOf[ScIomMessage]

        val v = md.cachedResult

        if(v != null && md.next == null) {
          return v
        }

        return md.withLocalsAndPerformOn(locals, locals)
	  }
    }
    state.ioNil
  }

  // Io: IoMessage_locals_seqArgAt_
  def localsAndSeqArgAt(locals: ScIomObject, at: int) : ScIomSymbol = {
    var v = localsAndValueArgAt(locals, at)

    if(!v.isInstanceOf[ScIomSymbol]) {
      // TODO: error here
    }

    return v.asInstanceOf[ScIomSymbol]
  }

  // Io: IoMessage_locals_performOn_
  override def withLocalsAndPerformOn(locals: ScIomObject, _target: ScIomObject) : ScIomObject = {
    var m = this
    var result = _target
    var target = _target

    do {
      println("evaluating: " + m)
      if(m.mname == state.semicolonSymbol) {
        target = locals
      } else {
        result = m.cachedResult

        println("have a result: " + result)

        if(result == null) {
          if(target.tag.performFunc == null) {
            result = target.perform(locals, m)
          } else {
            result = target.tag.performFunc(target, locals, m)
          }
        }

		target = result;

        println("target is: " + target)

        if(state.stopStatus != ScIomState.MESSAGE_STOP_STATUS_NORMAL) {
          return state.returnValue
        }
	  }
      m = m.next
    } while(m != null)

    result
  }

  // Io: IoMessage_addArg_
  def addArg(m: ScIomMessage) : Unit = args.append(m) 

  // Io: IoMessage_opShuffle_
  def opShuffle() : Unit = {
    if(rawGetSlot(state.opShuffleSymbol) != null && mname != state.noShufflingSymbol) {
      state.opShuffleMessage.withLocalsAndPerformOn(state.lobby, this)
    }
  }

  // Io: IoMessage_opShuffle
  def opShuffle(locals: ScIomObject, m: ScIomMessage) : ScIomMessage = {
println("DOING OP SHUFFLE")
    val levels = new Levels(this)
    val expressions = new ArrayBuffer[ScIomMessage]()
    expressions prepend this
    
    while(expressions.length >= 1) {
      var n = expressions.remove(0)

      do {
        levels.attach(n, expressions)

        for( x <- args.internalList ) 
          expressions += x.asInstanceOf[ScIomMessage]

        n = n.next
	  } while(n != null)

      levels.nextMessage
    }

    this
  }

  def asMinimalStackEntryDescription() = {
    StringUtils.newWithFormat("%s", List(mname.string).toArray)
  }
}
